using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;
using Konst3d.Efficiency.IO;
using Konst3d.Efficiency.Suite;

/* LAYOUT:
 * sign             /4b     0
 * version          /4b     4
 * years count      /4b     8
 * starting year    /4b     12
 * reserved         /400b   16
 * <year>                   
 * <year>
 * <year>
 */

/* YEAR LAYOUT:
 * <month>
 * <month>
 * <month>
 * ...
 */

/* MONTH LAYOUT: - all month have 31 days... just to simplify positioning in the file...
 * <day 0>
 * <day 1>
 * <day 2>
 * ...
 * <day 30>
 */

/* DAY LAYOUT:
 * active /1b
 * commentaryID /4b
 * value /4b (float)
 */

namespace Konst3d.Efficiency.Suite.SparsedBinary
{
    // TOFIX: should be devided onto StatisticStorage & StatisticFile ...

    internal class StatisticStorage : BinaryFile, IStatisticStorage, IDisposable
    {
        #region Constants

        private const uint SIGNATURE = 0x03030303;
        private const uint VERSION = 1;
        private const uint CAPACITY = 100;
        private const int DAYS_IN_MONTH = 31;
        private const int DAYS_IN_YEAR = DAYS_IN_MONTH * 12;
        private const int DAY_RECORD_BYTE_SIZE = 9;
        private const int STATISTIC_DATA_OFFSET = 416;

        #endregion // Constants

        #region Properties

        public uint Capacity
        {
            get;
            private set;
        }

        #endregion // Properties

        #region Create/Load Methods

        public static StatisticStorage Create(string path, bool overwrite, LabelsStorage labels, ActiveDaysStorage activeDays)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));
            Contract.Requires(labels != null);
            Contract.Requires(activeDays != null);

            if (File.Exists(path) && !overwrite)
            {
                throw new IOException(String.Format("File \"{0}\" is already exists."));
            }

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                writer.Write(SIGNATURE);
                writer.Write(VERSION);
                writer.Write(CAPACITY);
                writer.Write(DateTime.Now.Year);
                writer.WriteBytes(0, 400);

                for (int year = 0; year < CAPACITY; ++year)
                {
                    for (int month = 0; month < 12; ++month)
                    {
                        for (int day = 0; day < 31; ++day)
                        {
                            if (year == 0 && ((month < DateTime.Now.Month - 1) || (month == DateTime.Now.Month - 1 && day < DateTime.Now.Day - 1)))
                            {
                                writer.Write((byte)0);
                            }
                            else
                            {
                                writer.Write((byte)1);
                            }

                            writer.Write((int)-1);
                            writer.Write((float)-1);
                        }
                    }
                }

                writer.Flush();
                writer.Seek(0, SeekOrigin.Begin);

                return new StatisticStorage(stream, reader, writer, labels, activeDays);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        public static StatisticStorage Open(string path, LabelsStorage labels, ActiveDaysStorage activeDays)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));
            Contract.Requires(labels != null);
            Contract.Requires(activeDays != null);
            Contract.Requires(File.Exists(path));

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                return new StatisticStorage(stream, reader, writer, labels, activeDays);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        #endregion // Create/Load Methods

        #region Constructor

        private StatisticStorage(FileStream stream, BinaryReader reader, BinaryWriter writer, LabelsStorage labels, ActiveDaysStorage activeDays):
            base(stream, reader, writer, SIGNATURE, VERSION)
        {
            Contract.Requires(labels != null);
            Contract.Requires(activeDays != null);

            _labels = labels;
            _activeDays = activeDays;

            Capacity = _reader.ReadUInt32();
            StartingYear = _reader.ReadInt32();

            for (int i = StartingYear; i < StartingYear + Capacity; ++i)
            {
                _cache.Add(i, new StatisticYear(i, _reader, _writer, STATISTIC_DATA_OFFSET + DAYS_IN_YEAR * DAY_RECORD_BYTE_SIZE * (i - StartingYear), _labels));
            }

            _cache[DateTime.Now.Year].Load(); // TOFIX: what if monitor was stopped years ago?
        }

        #endregion // Constructor

        #region IMonitoringStatisticStorage Members

        public int StartingYear
        {
            get;
            private set;
        }

        public IStatisticDay GetDay(DateTime date)
        {
            Contract.Assert(!_disposed);
            Contract.Assert(ContainsDay(date));

            return _cache[date.Year].GetDay(date);
        }

        public IStatisticDay[] GetRange(DateTime begin, DateTime end)
        {
            Contract.Requires(end.Date > begin.Date);
            Contract.Assert(ContainsDay(begin.Date) && ContainsDay(end.Date));

			var days = new IStatisticDay[(int)Math.Ceiling((end.Date - begin.Date).TotalDays) + 1];

            for (int i = 0; i < days.Length; ++i)
            {
                days[i] = GetDay(begin.Date.AddDays(i));
            }

            return days;
        }

        public double GetSummaryValue(DateTime begin, DateTime end)
        {
            Contract.Assert(ContainsDay(begin));
            Contract.Assert(ContainsDay(end));

            double summary = 0;

            for (var date = begin; date <= end; date = date.AddDays(1))
            {
                var day = GetDay(date);

                if (day.Active && _activeDays.GetDayOfWeek((int)day.Date.DayOfWeek) && _activeDays.GetDayOfMonth(day.Date.Day - 1))
                {
                    if (!Comparer.Equals(day.Value, -1.0f))
                    {
                        summary += day.Value;
                    }
                }
            }

            return summary;
        }

        public double GetAverageValue(DateTime begin, DateTime end)
        {
            Contract.Assert(ContainsDay(begin));
            Contract.Assert(ContainsDay(end));

            double summary = 0;
            int count = 0;

            for (var date = begin; date <= end; date = date.AddDays(1))
            {
                var day = GetDay(date);

                if (day.Active && _activeDays.GetDayOfWeek((int)day.Date.DayOfWeek) && _activeDays.GetDayOfMonth(day.Date.Day - 1))
                {
                    if (!Comparer.Equals(day.Value, -1.0f)) // not set?
                    {
                        summary += day.Value;
                        count += 1;
                    }
                }
            }

            return count > 0 ? summary / (double)count : 0;
        }

        public void ForEach(StatisticDayCallback callback)
        {
            Contract.Requires(callback != null);

            ForEach(new DateTime(StartingYear, 1, 1), DateTime.Now, callback);
        }

        public void ForEach(DateTime begin, DateTime end, StatisticDayCallback callback)
        {
            Contract.Requires(callback != null);
            Contract.Requires(end >= begin);

            for (var date = begin; date <= end; date = date.AddDays(1))
            {
                callback(GetDay(date));
            }
        }

        #endregion

        #region Private Methods

        private bool ContainsDay(DateTime date)
        {
            return date.Year >= StartingYear && date.Year <= StartingYear + Capacity;
        }

        #endregion // Private Methods

        #region IDisposable Members

        protected override void DisposeInternal(bool disposing)
        {
            base.DisposeInternal(disposing);

            if (disposing)
            {
                foreach (var year in _cache.Values)
                {
                    year.Dispose();
                }

                _cache.Clear();
                _cache = null;
            }
        }

        #endregion

        #region Fields

        private LabelsStorage _labels;
        private ActiveDaysStorage _activeDays;
        private Dictionary<int, StatisticYear> _cache = new Dictionary<int, StatisticYear>();

        #endregion // Fields
    }
}
